<?php
namespace classlib\store;
class awss3 {
	/**
	 * 定义请求的Access key
	 * var string $key
	 */
	private $key;
	/**
	 * 定义请求的Access secret
	 * var string $privateKey
	 */ 
	private $privateKey;
	/**
	 * 请求亚马逊的服务地址
	 * var string $host
	 */
	private $host;
	/**
	 * 请求的日期格式地址
	 * var string $date
	 */
	private $date;
	/**
	 * 发起请求之后的返回信息
	 * var array $curlInfo
	 */
	private $curlInfo;
	
	/**
	 * 保留的上传文件格式限制
	 * var array $mimeTypes
	 */
	private $mimeTypes = array(
		"txt" => "text/plain", "css" => "text/css", "js" => "application/x-javascript", "html" => "text/html", "htm" => "text/html",
		"bmp" => "image/bmp", "jpe" => "image/jpeg", "jpeg" => "image/jpeg", "jpg" => "image/jpeg", "gif" => "image/gif", 
		"png" => "image/png", "ico" => "image/x-icon", "zip" => "application/zip", "gz" => "application/x-gzip");
	
	/**
	 * 构造函数初始化
	 * @param string $key
	 * @param string $private_key
	 * @param string $host
	 **/
	public function __construct($key, $private_key, $host = 's3.amazonaws.com') {
		$this->key        = $key;
		$this->privateKey = $private_key;
		$this->host       = $host;
		$this->date       = gmdate('D, d M Y H:i:s T');
	}
	
	/**
	 * 上传本地文件的S3服务器上
	 * @param string $bucket_name
	 * @param string $s3_path
	 * @param string $fs_path
	 * @param bool   $web_accessible
	 * @param array  $header
	 * @return bool true success
	 **/
	public function uploadFile($bucket_name, $s3_path, $fs_path, $web_accessible = true, $headers = array()) {
		$s3_path   = '/'.$bucket_name.$s3_path;
		$request   = array('verb' => 'PUT', 'resource' => $s3_path,
				'content-md5' => $this->base64(md5_file($fs_path)));
		$fh        = fopen($fs_path, 'r');
		$curl_opts = array('CURLOPT_PUT'=>true, 'CURLOPT_INFILE'=>$fh,
			'CURLOPT_INFILESIZE'=>filesize($fs_path), 'CURLOPT_CUSTOMREQUEST'=>'PUT');
		
		$headers['Content-MD5'] = $request['content-md5'];
		if($web_accessible === true) {
			$headers['x-amz-acl'] = 'public-read';
		}
		$ext = pathinfo($fs_path, PATHINFO_EXTENSION);
		$headers['Content-Type'] = isset($this->mimeTypes[$ext]) ? $this->mimeTypes[$ext]:'application/octet-stream';
		$request['content-type'] = $headers['Content-Type'];
		$result = $this->sendRequest($request, $headers, $curl_opts);
		if ($this->curlInfo['http_code'] != '200') {
			\classlib\logger::inst()->write($result);
		}
		fclose($fh);
		return $this->curlInfo['http_code'] == '200';
	}
	
	/**
	 * 获取一个对象实例信息
	 * @param string $bucket_name
	 * @param string $s3_path
	 * @return array
	 */
	public function getObjectInfo($bucket_name, $s3_path) {
		$request   = array('verb' => 'HEAD', 'resource' => $s3_path);
		$curl_opts = array('CURLOPT_HEADER' => true, 'CURLOPT_NOBODY' => true);
		$result    = $this->sendRequest($request, null, $curl_opts);
		$xml       = @simplexml_load_string($result);
		if($xml !== false) {
			return false;
		}
		preg_match_all('/^(\S*?): (.*?)$/ms', $result, $matches);
		$info = array();
		for($i = 0; $i < count($matches[1]); $i++) {
			$info[$matches[1][$i]] = trim($matches[2][$i]);
		}
		return $info;
	}
	
	/**
	 * 删除服务器上的一个文件信息
	 * @param string $bucket_name
	 * @param string $s3_path
	 * @return bool true success
	 */
	public function deleteObject($bucket_name, $s3_path) {
		$s3_path = '/'.$bucket_name.$s3_path;
		$request = array('verb' => 'DELETE', 'resource' => $s3_path);
		$result  = $this->sendRequest($request);
		return $this->curlInfo['http_code'] == '204';
	}
		
	/**
	 * 真实的发起服务器请求到S3服务器
	 * @param array $request
	 * @param array $headers 
	 * @param array $curl_opts
	 * @return string
	 */
	private function sendRequest($request, $headers = null, $curl_opts = null) {
		if(is_null($headers)) {
			$headers = array();
		}
		$headers['Date'] 		  = $this->date;
		$headers['Authorization'] = 'AWS ' . $this->key . ':' . $this->signature($request, $headers);
		foreach($headers as $k => $v) {
			$headers[$k] = "$k: $v";
		}
		$uri = 'http://' . $this->host . $request['resource'];
		$ch  = curl_init();
		curl_setopt($ch, CURLOPT_URL, $uri);
		curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $request['verb']);
		curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
	
		if(is_array($curl_opts)) {
			foreach($curl_opts as $k => $v) {
				curl_setopt($ch, constant($k), $v);
			}
		}

		$result = curl_exec($ch);
		$this->curlInfo = curl_getinfo($ch);
		curl_close($ch);
		return $result;
	}
	
	/**
	 * 生成请求的认证签名资料
	 * @param array $request
	 * @param array $header
	 * @return string
	 */
	private function signature($request, $headers = array()) {
		if(is_null($headers)) {
			$headers = array();
		}
        $CanonicalizedAmzHeadersArr = array();
        $CanonicalizedAmzHeadersStr = '';
        foreach($headers as $k => $v) {
			$k = strtolower($k);
			if(substr($k, 0, 5) != 'x-amz') {
				continue;
			}
			if(isset($CanonicalizedAmzHeadersArr[$k])) {
				$CanonicalizedAmzHeadersArr[$k] .= ',' . trim($v);
			} else {
				$CanonicalizedAmzHeadersArr[$k] = trim($v);
            }
        }
		ksort($CanonicalizedAmzHeadersArr);
		foreach($CanonicalizedAmzHeadersArr as $k => $v) {
	 		$CanonicalizedAmzHeadersStr .= "$k:$v\n";
		}
		$str  = $request['verb'] . "\n";
		$str .= isset($request['content-md5']) ? $request['content-md5'] . "\n" : "\n";
		$str .= isset($request['content-type']) ? $request['content-type'] . "\n" : "\n";
		$str .= isset($request['date']) ? $request['date']  . "\n" : $this->date . "\n";
		$str .= $CanonicalizedAmzHeadersStr . preg_replace('/\?.*/', '', $request['resource']);
		$sha1 = $this->hasher($str);
		return $this->base64($sha1);
	}
	
	/**
	 * 对数据进行算法加密
	 * @param string $data
	 * @return string
	 */
	private function hasher($data) {
		$key     = $this->privateKey;
		if(strlen($key) > 64) {
			$key = pack('H40', sha1($key));
		}
		if(strlen($key) < 64) {
			$key = str_pad($key, 64, chr(0));
		}
		$ipad = (substr($key, 0, 64) ^ str_repeat(chr(0x36), 64));
		$opad = (substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64));
		return sha1($opad . pack('H40', sha1($ipad . $data)));
	}
	
	/**
	 * 对字符串做Base64 处理
	 * @param string $str
	 * @return string
	 */
	private function base64($str) {
		$ret = '';
		for($i = 0; $i < strlen($str); $i += 2) {
			$ret .= chr(hexdec(substr($str, $i, 2)));
		}
		return base64_encode($ret);
	}
}